﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;

namespace SimManning
{
	/// <summary>
	/// A task to be simulated. Almost everything is represented as a task (of different <see cref="TaskType"/>),
	/// including rest, sleep, weather events, etc.
	/// </summary>
	public abstract partial class SimulationTask
	{
		readonly int id;

		/// <summary>
		/// Public identifier of this Task
		/// </summary>
		public int Id
		{
			get { return this.id; }
		}

		/// <summary>
		/// Internal sequential ID used solely by the Simulation engine.
		/// </summary>
		internal int InternalId;

		string name;

		/// <summary>
		/// The name of the task.
		/// A suffix may be added by <see cref="SimulationDataSet.AutoExpandTasks"/>
		/// iff the task has <see cref="AutoExpandToAllCrewmen"/> set to true.
		/// </summary>
		public string Name
		{
			get { return this.name; }
			set { this.name = value; }
		}

		/// <summary>
		/// Get the name of the task without a suffix possibly added by <see cref="SimulationDataSet.AutoExpandTasks"/>.
		/// </summary>
		public string NameNotExpanded
		{
			get
			{
				var i = this.name.IndexOf('|');
				if (i < 0) return this.name;
				else return this.name.Substring(0, i);
			}
		}

		string description;

		public string Description
		{
			get { return this.description; }
			set { this.description = value; }
		}

		/// <summary>
		/// Cached information inferred from TaskType.
		/// </summary>
		bool isWork;

		public bool IsWork
		{
			get { return this.isWork; }
		}

		bool isPhaseDependent;

		public bool IsPhaseDependent
		{
			get { return this.isPhaseDependent; }
		}

		/// <summary>
		/// Cached information inferred from TaskType.
		/// </summary>
		int taskTypeSubCode1;

		public int TaskTypeSubCode1
		{
			get { return this.taskTypeSubCode1; }
		}

		int taskType;

		public int TaskType
		{
			get { return this.taskType; }
			set
			{
				this.taskType = value;
				this.taskTypeSubCode1 = value.SubCode();
				switch (this.taskTypeSubCode1)
				{
					case (int)StandardTaskType.Idle:
					case (int)StandardTaskType.Rest:
						this.isWork = false;
						break;
					default:
						this.isWork = true;
						break;
				}
			}
		}

		readonly SimulationTask refTask;

		public SimulationTask RefTask	//TODO: Try to remove
		{
			get { return this.refTask; }
		}

		/*/// <summary>
		/// Indicates if the task should be automatically generated by the system (true) because it is obligatory for some internal algorithms,
		/// or is just a normal user task (false).
		/// </summary>
		/// <remarks>
		/// By convention, system task's name should start by a '!'.
		/// </remarks>
		public bool systemTask;*/

		bool autoExpandToAllCrewmen;

		public bool AutoExpandToAllCrewmen
		{
			get { return this.autoExpandToAllCrewmen; }
			set { this.autoExpandToAllCrewmen = value; }
		}

		/// <summary>
		/// Return true if this is an additional tasks resulting from the auto-expansion of a with attribute <see cref="AutoExpandToAllCrewmen"/> set to true, false otherwise.
		/// </summary>
		/// <remarks>
		/// Additional auto-expanded tasks have a name containing a vertical bar '|'.
		/// </remarks>
		public bool IsAutoExpanded
		{
			get { return this.name.IndexOf('|') >= 0; }
		}

		RelativeDateType relativeDate;

		public RelativeDateType RelativeDate
		{
			get { return this.relativeDate; }
			set
			{
				this.relativeDate = value;
				this.isPhaseDependent = this.relativeDate != RelativeDateType.Frequency;
			}
		}

		public TimeDistribution StartDate;	//Do not use a property for structures, otherwise the TimeDistribution struct will not be updatable

		RelativeTimeType relativeTime = RelativeTimeType.TimeWindow;

		public RelativeTimeType RelativeTime
		{
			get { return this.relativeTime; }
			set { this.relativeTime = value; }
		}

		public TimeDistribution DateOffset;

		public SimulationTime DailyHourStart;

		public SimulationTime DailyHourEnd;

		bool onHolidays;

		public bool OnHolidays
		{
			get { return this.onHolidays; }
			set { this.onHolidays = value; }
		}

		public TimeDistribution Duration;

		TaskInterruptionPolicies taskInterruptionPolicy;

		public TaskInterruptionPolicies TaskInterruptionPolicy
		{
			get { return this.taskInterruptionPolicy; }
			set { this.taskInterruptionPolicy = value; }
		}

		PhaseInterruptionPolicies phaseInterruptionPolicy;

		public PhaseInterruptionPolicies PhaseInterruptionPolicy
		{
			get { return this.phaseInterruptionPolicy; }
			set { this.phaseInterruptionPolicy = value; }
		}

		ScenarioInterruptionPolicies scenarioInterruptionPolicy = ScenarioInterruptionPolicies.DropWithoutError;

		public ScenarioInterruptionPolicies ScenarioInterruptionPolicy
		{
			get { return this.scenarioInterruptionPolicy; }
			set { this.scenarioInterruptionPolicy = value; }
		}

		InterruptionTypes interruptionErrorPolicy;

		/// <summary>
		/// (Not used yet) Defines what actions to undertake when a task is interrupted by another task of higher priority.
		/// </summary>
		[Obsolete("Not used/implemented yet!")]
		public InterruptionTypes InterruptionErrorPolicy	//TODO: Use InterruptionErrorPolicy
		{
			get { return this.interruptionErrorPolicy; }
			set { this.interruptionErrorPolicy = value; }
		}

		int priority;

		/// <summary>
		/// Priority of the task relatively to the other tasks.
		/// </summary>
		public int Priority
		{
			get { return this.priority; }
			set { this.priority = value; }
		}

		bool enabled = true;

		/// <summary>
		/// Specifies if the task is used or not in the scenario.
		/// If false, the task will not act like non-existing.
		/// </summary>
		public bool Enabled
		{
			get { return this.RootTask.enabled; }
			set
			{
				this.enabled = value;
				this.RootTask.enabled = value;
			}
		}

		readonly List<int> phaseTypes;

		/// <summary>
		/// List of phase types in which the task is always active.
		/// </summary>
		public List<int> PhaseTypes
		{
			get { return this.phaseTypes; }
		}

		readonly List<int> crewmanTypes;

		/// <summary>
		/// List of crewman types more likelly to be assigned to the task.
		/// </summary>
		[Obsolete("Not used!")]
		public List<int> CrewmanTypes
		{
			get { return this.crewmanTypes; }
		}

		/*readonly List<TaskRelation> relations;

		internal IEnumerable<TaskRelation> Relations
		{
			get { return this.relations; }
		}*/

		int numberOfCrewmenNeeded;

		public int NumberOfCrewmenNeeded
		{
			get { return this.numberOfCrewmenNeeded; }
			set { this.numberOfCrewmenNeeded = value; }
		}

		/// <summary>
		/// Rotation duration. Set to zero to disable rotation.
		/// </summary>
		public SimulationTime Rotation;

		TaskDuplicatesPolicy duplicatesPolicy;

		/// <summary>
		/// Policy to handle duplicates, i.e. when a new instance of a task is created
		/// when the previous instance(s) is not finished.
		/// </summary>
		public TaskDuplicatesPolicy DuplicatesPolicy
		{
			get { return this.duplicatesPolicy; }
			set { this.duplicatesPolicy = value; }
		}

		[Obsolete("Use DuplicatesPolicy instead!")]
		public bool NoDuplicate
		{
			get { return this.duplicatesPolicy != TaskDuplicatesPolicy.KeepDuplicates; }
			set { this.duplicatesPolicy = value ? TaskDuplicatesPolicy.KillOldDuplicates : TaskDuplicatesPolicy.KeepDuplicates; }
		}

		/*[Obsolete("Use DuplicatesPolicy instead!")]
		public bool NeedsDuplicateManagement
		{
			get
			{
				return this.NoDuplicate ||
					((this.numberOfCrewmenNeeded <= 1) &&	//No optimisation currently for tasks with more than 1 crewman needed
						((this.taskInterruptionPolicy & (TaskInterruptionPolicies.ContinueOrResumeWithError | TaskInterruptionPolicies.ContinueOrResumeWithoutError)) == this.taskInterruptionPolicy));
			}
		}*/

		readonly Dictionary<int, SimulationTask> parallelTasks;

		public Dictionary<int, SimulationTask> ParallelTasks
		{//TODO: When using time distribution, set the same values for all parallel tasks
			get { return this.parallelTasks; }
		}

		readonly Dictionary<int, SimulationTask> slaveTasks;

		public Dictionary<int, SimulationTask> SlaveTasks
		{
			get { return this.slaveTasks; }
		}

		readonly Dictionary<int, SimulationTask> masterTasks;

		public Dictionary<int, SimulationTask> MasterTasks
		{
			get { return this.masterTasks; }
		}

		protected SimulationTask(int id)
		{
			this.id = id;
			this.phaseTypes = new List<int>();
			this.crewmanTypes = new List<int>();
			this.parallelTasks = new Dictionary<int, SimulationTask>();
			this.slaveTasks = new Dictionary<int, SimulationTask>();
			this.masterTasks = new Dictionary<int, SimulationTask>();
			this.simulationCurrentQualifications = new Dictionary<Crewman, byte>();
		}

		/// <summary>
		/// Create a new task based on a reference task from which the different parameters will be copied.
		/// </summary>
		/// <param name="id">The ID of the new task, potentially different from the reference task.</param>
		/// <param name="refTask">The parent task from which parameters will be copied</param>
		/// <param name="linkingType">Controls the possible sharing with the reference task some data structure</param>
		protected SimulationTask(int id, SimulationTask refTask, TaskLinkingType linkingType)
		{
			this.id = id;
			this.refTask = refTask;
			Reset();
			{//Always linked
				this.crewmanTypes = refTask.crewmanTypes;
				this.phaseTypes = refTask.phaseTypes;
			}
			switch (linkingType)
			{
				case TaskLinkingType.Linked:
					this.simulationCurrentQualifications = refTask.simulationCurrentQualifications;
					this.parallelTasks = refTask.parallelTasks;
					this.slaveTasks = refTask.slaveTasks;
					this.masterTasks = refTask.masterTasks;
					break;
				case TaskLinkingType.Copy:
					//this.crewmanTypes = refTask.crewmanTypes.ToList();
					//this.phaseTypes = refTask.phaseTypes.ToList();
					this.simulationCurrentQualifications = new Dictionary<Crewman, byte>(this.refTask.simulationCurrentQualifications);
					this.parallelTasks = new Dictionary<int, SimulationTask>(this.refTask.parallelTasks);
					this.slaveTasks = new Dictionary<int, SimulationTask>(this.refTask.slaveTasks);
					this.masterTasks = new Dictionary<int, SimulationTask>(this.refTask.masterTasks);
					break;
				case TaskLinkingType.Clear:
				default:
					//this.crewmanTypes = refTask.crewmanTypes.ToList();
					//this.phaseTypes = refTask.phaseTypes.ToList();
					this.simulationCurrentQualifications = new Dictionary<Crewman, byte>(this.refTask.simulationCurrentQualifications);
					this.parallelTasks = new Dictionary<int, SimulationTask>();
					this.slaveTasks = new Dictionary<int, SimulationTask>();
					this.masterTasks = new Dictionary<int, SimulationTask>();
					break;
			}
			this.refTask = refTask.RootTask;
		}

		/// <summary>
		/// Create a new task based on a reference task from which the different parameters will be copied (including task ID).
		/// </summary>
		/// <param name="refTask">The parent task from which parameters will be copied</param>
		/// <param name="linkingType">Controls the possible sharing with the reference task some data structures</param>
		protected SimulationTask(SimulationTask refTask, TaskLinkingType linkingType) : this(refTask.id, refTask, linkingType) { }

		/// <summary>
		/// Reset to the default parameters of the parent Task, except for readonly attributes (e.g. this.id) and lists.
		/// </summary>
		public void Reset()
		{
			if (this.refTask == null)
			{
				this.name = "!Invalid";
				this.TaskType = (int)default(StandardTaskType);
				this.taskInterruptionPolicy = TaskInterruptionPolicies.Undefined;
				this.phaseInterruptionPolicy = PhaseInterruptionPolicies.Undefined;
				this.scenarioInterruptionPolicy = ScenarioInterruptionPolicies.Undefined;
				return;
			}
			this.InternalId = refTask.InternalId;
			this.name = this.refTask.name;
			this.TaskType = this.refTask.taskType;
			//this.systemTask = this.refTask.systemTask;
			this.autoExpandToAllCrewmen = this.refTask.autoExpandToAllCrewmen;
			this.RelativeDate = this.refTask.relativeDate;
			this.DateOffset = this.refTask.DateOffset;
			this.StartDate = this.refTask.StartDate;
			this.relativeTime = this.refTask.relativeTime;
			this.DailyHourStart = this.refTask.DailyHourStart;
			this.DailyHourEnd = this.refTask.DailyHourEnd;
			this.onHolidays = this.refTask.onHolidays;
			this.Duration = this.refTask.Duration;
			this.taskInterruptionPolicy = this.refTask.taskInterruptionPolicy;
			this.phaseInterruptionPolicy = this.refTask.phaseInterruptionPolicy;
			this.scenarioInterruptionPolicy = this.refTask.scenarioInterruptionPolicy;
			this.interruptionErrorPolicy = this.refTask.interruptionErrorPolicy;
			this.priority = this.refTask.priority;
			this.numberOfCrewmenNeeded = this.refTask.numberOfCrewmenNeeded;
			this.Rotation = this.refTask.Rotation;
			this.description = this.refTask.description;
			this.duplicatesPolicy = this.refTask.duplicatesPolicy;
		}

		public SimulationTask RootTask
		{
			get
			{
				var rootTask = this;
				var myTaskRef = this.refTask;
				while (myTaskRef != null)
				{
					rootTask = myTaskRef;
					myTaskRef = rootTask.refTask;
				}
				return rootTask;
			}
		}

		public virtual bool Identical(SimulationTask task2)
		{
			if ((task2 == null) || (this.id != task2.id) || (this.name != task2.name) ||	//Do not compare description
				((!((this.Duration.Unit == TimeUnit.Undefined) && (task2.Duration.Unit == TimeUnit.Undefined))) && (this.Duration != task2.Duration)) ||
				/*(this.systemTask != task2.systemTask) ||*/ (this.autoExpandToAllCrewmen != task2.autoExpandToAllCrewmen) ||
				(this.taskInterruptionPolicy != task2.taskInterruptionPolicy) || (this.phaseInterruptionPolicy != task2.phaseInterruptionPolicy) || (this.scenarioInterruptionPolicy != task2.scenarioInterruptionPolicy) ||
				(this.interruptionErrorPolicy != task2.interruptionErrorPolicy) ||
				(this.duplicatesPolicy != task2.duplicatesPolicy) || (this.Rotation != task2.Rotation) || (this.numberOfCrewmenNeeded != task2.numberOfCrewmenNeeded) ||
				(this.onHolidays != task2.onHolidays) || (this.priority != task2.priority) || (this.relativeDate != task2.relativeDate) ||
				(this.taskType != task2.taskType) || (this.relativeTime != task2.relativeTime) ||
				((!((this.StartDate.Unit == TimeUnit.Undefined) && (task2.StartDate.Unit == TimeUnit.Undefined))) && (this.StartDate != task2.StartDate)) ||
				((!((this.DateOffset.Unit == TimeUnit.Undefined) && (task2.DateOffset.Unit == TimeUnit.Undefined))) && (this.DateOffset != task2.DateOffset)) ||
				(this.DailyHourEnd != task2.DailyHourEnd) || (this.DailyHourStart != task2.DailyHourStart) ||
				(!this.phaseTypes.OrderBy(i => i).SequenceEqual(task2.phaseTypes.OrderBy(i => i))) || (!this.crewmanTypes.SequenceEqual(task2.crewmanTypes)) ||
				(this.parallelTasks.Count != task2.parallelTasks.Count) || (this.slaveTasks.Count != task2.slaveTasks.Count) || (this.masterTasks.Count != task2.masterTasks.Count))
				return false;
			foreach (var otherTaskId in this.parallelTasks.Values.Select(t => t.id))
				if (!task2.parallelTasks.ContainsKey(otherTaskId))
					return false;
			foreach (var otherTaskId in this.slaveTasks.Values.Select(t => t.id))
				if (!task2.slaveTasks.ContainsKey(otherTaskId))
					return false;
			foreach (var otherTaskId in this.masterTasks.Values.Select(t => t.id))
				if (!task2.masterTasks.ContainsKey(otherTaskId))
					return false;
			return true;
		}

		/// <summary>
		/// Returns true if the task must be assigned to someone, false otherwise.
		/// </summary>
		public virtual bool EnforceAssignment
		{
			get
			{
				switch (this.taskTypeSubCode1)
				{
					case (int)StandardTaskType.Idle:
					case (int)StandardTaskType.Rest:
						return false;
					default:
						return true;
				}
			}
		}

		/// <summary>
		/// Tells if the task correctly assigned in a given crew or not.
		/// </summary>
		/// <param name="crew">A crew</param>
		/// <returns>True if the task is assigned to enough crewmen in the given crew, false otherwise.</returns>
		public bool IsAssigned(Crew crew)
		{
			int needed = this.numberOfCrewmenNeeded;
			if (needed <= 0) return true;
			if (crew == null) return false;
			if (this.autoExpandToAllCrewmen) return true;
			byte percentage;
			if (this.numberOfCrewmenNeeded > 0)
			{
				foreach (var crewman in crew.Values.Where(cm => cm.Id > 0))
					if (crewman.Qualifications.TryGetValue(this.id, out percentage) && (percentage > 0))
					{
						needed--;
						if (needed <= 0) break;
					}
				return needed <= 0;
			}
			else	//A task with no crew-member needed must not have anybody assigned.
			{
				foreach (var crewman in crew.Values.Where(cm => cm.Id > 0))
					if (crewman.Qualifications.TryGetValue(this.id, out percentage) && (percentage > 0))
						return false;
				return true;
			}
		}

		/// <summary>
		/// Tells if the task is allowed to be executed in a given phase or not.
		/// </summary>
		/// <param name="phase">A phase</param>
		/// <returns>True if the task is allowed to be executed in the given phase, false otherwise.</returns>
		public bool Allowed(Phase phase)
		{
			return phase.Tasks.ContainsKey(this.id) || this.phaseTypes.Any(pt => phase.PhaseType.IsSubCodeOf(pt)) ||
				this.masterTasks.Values.Any(t => t.Allowed(phase));	//Recursion
		}

		public void Validate()
		{
			#pragma warning disable 618	//Disable warning for obsolete "DoNotInterrupt"
			if (this.phaseInterruptionPolicy == PhaseInterruptionPolicies.DoNotInterrupt)
			#pragma warning restore 618
			{//Back compatibility
				this.taskInterruptionPolicy = TaskInterruptionPolicies.ContinueOrResumeWithError;
				this.phaseInterruptionPolicy = PhaseInterruptionPolicies.ContinueOrDropWithError;
			}
			if (this.taskType.IsSubCodeOf(StandardTaskType.ExternalCondition) ||
				this.taskType.IsSubCodeOf(StandardTaskType.CriticalEvents))
			{
				this.taskInterruptionPolicy = TaskInterruptionPolicies.DropWithError;
				this.phaseInterruptionPolicy = PhaseInterruptionPolicies.ResumeOrDropWithError;
				this.scenarioInterruptionPolicy = ScenarioInterruptionPolicies.DropWithoutError;
				//this.relativeTime = Task.RelativeTimeType.TimeWindow;
				//this.dailyHourStart = TimeSpan.Zero;
				//this.dailyHourEnd = TimeSpan.Zero;
				//this.onHolidays = true;
				this.autoExpandToAllCrewmen = false;
				this.numberOfCrewmenNeeded = 0;
				this.Rotation = SimulationTime.Zero;
				this.duplicatesPolicy = TaskDuplicatesPolicy.MergeDuplicates;
				this.masterTasks.Clear();
				this.parallelTasks.Clear();
				this.priority = 700;
			}
			else
			{
				this.slaveTasks.Clear();	//Only external conditions can have slaves
				if (this.relativeDate == RelativeDateType.TriggeredByAnEvent)
				{
					this.StartDate = TimeDistribution.Zero;
					this.Duration = new TimeDistribution(SimulationTime.ArbitraryLargeDuration);
					this.duplicatesPolicy = TaskDuplicatesPolicy.MergeDuplicates;
					this.parallelTasks.Clear();
					this.phaseTypes.Clear();
				}
				else this.masterTasks.Clear();
				if (this.phaseInterruptionPolicy == PhaseInterruptionPolicies.WholePhase)
				{
					this.DailyHourStart = SimulationTime.Zero;
					this.DailyHourEnd = SimulationTime.Zero;
					this.RelativeDate = RelativeDateType.RelativeStartFromStartOfPhase;
					this.StartDate = TimeDistribution.Zero;
					this.Duration = new TimeDistribution(SimulationTime.ArbitraryLargeDuration);
					this.parallelTasks.Clear();
				}
				else if ((this.phaseInterruptionPolicy == PhaseInterruptionPolicies.Obligatory) &&
					(this.relativeDate != RelativeDateType.RelativeStartFromStartOfPhase) &&
					(this.relativeDate != RelativeDateType.RelativeStartFromEndOfPhase) &&
					(this.relativeDate != RelativeDateType.RelativeStopFromEndOfPhase))
					this.phaseInterruptionPolicy = PhaseInterruptionPolicies.DropWithError;
				switch (this.relativeDate)
				{
					case RelativeDateType.RelativeStopFromEndOfPhase:
					case RelativeDateType.RelativeStartFromStartOfPhase:
					case RelativeDateType.RelativeStartFromEndOfPhase:
					case RelativeDateType.RelativeStartFromPreviousStart:
						this.DateOffset = default(TimeDistribution);
						break;
				}
				if (((this.relativeDate == RelativeDateType.AbsoluteStartMonthDay) ||
					(this.relativeDate == RelativeDateType.AbsoluteStartWeekDay)))
					this.StartDate.Unit = TimeUnit.Days;
				if (this.autoExpandToAllCrewmen)
				{
					this.numberOfCrewmenNeeded = 1;
					this.Rotation = SimulationTime.Zero;
				}
			}
			this.Duration.Validate();
			this.StartDate.Validate();
		}

		public bool Valid
		{
			get { return this.enabled && String.IsNullOrEmpty(this.ErrorMessage); }
		}

		public virtual string ErrorMessage
		{
			get
			{
				if (this.id == 0)
					return "Invalid task ID!";
				if ((this.taskType <= (int)StandardTaskType.Idle) ||
					(this.Duration.Unit == TimeUnit.Undefined) ||
					(this.taskInterruptionPolicy == TaskInterruptionPolicies.Undefined) ||
					(this.phaseInterruptionPolicy == PhaseInterruptionPolicies.Undefined) ||
					(this.scenarioInterruptionPolicy == ScenarioInterruptionPolicies.Undefined) ||
					(this.relativeDate == RelativeDateType.Undefined) ||
					(this.relativeTime == RelativeTimeType.Undefined) ||
					(this.StartDate.Unit == TimeUnit.Undefined))
					return "Some types are undefined!";
				if ((this.priority <= 0) || (this.priority > 1000))
					return "Priority out of range ]0..1000]!";
				if (this.taskType.IsSubCodeOf(StandardTaskType.ExternalCondition) || this.taskType.IsSubCodeOf(StandardTaskType.CriticalEvents))
				{
					if (this.Duration.Distribution == ProbabilityDistribution.Exponential)
						return "Only external conditions and critical events can have a duration defined as an exponential distribution (0, mean, 0)!";
				}
				else if (this.slaveTasks.Count > 0)
					return "Only external conditions and critical events can have slaves!";
				if ((this.phaseInterruptionPolicy == PhaseInterruptionPolicies.WholePhase) &&
					((this.relativeDate != RelativeDateType.RelativeStartFromStartOfPhase) || (this.StartDate.MaxPossible > SimulationTime.Zero)))
					return "Tasks lasting “WholePhase” must be with a date “RelativeStartFromStartOfPhase, 0”!";
				if ((this.phaseInterruptionPolicy == PhaseInterruptionPolicies.Obligatory) &&
					(this.relativeDate != RelativeDateType.RelativeStartFromStartOfPhase) &&
					(this.relativeDate != RelativeDateType.RelativeStartFromEndOfPhase) &&
					(this.relativeDate != RelativeDateType.RelativeStopFromEndOfPhase))
					return "Obligatory tasks must have a date relative to the start or the end of the phase!";
				if (((this.relativeDate == RelativeDateType.AbsoluteStartMonthDay) ||
					(this.relativeDate == RelativeDateType.AbsoluteStartWeekDay)) &&
					(this.StartDate.Unit != TimeUnit.Days))
					return "Tasks starting on a specific day must use “Days” as time unit!";
				if ((this.relativeDate == RelativeDateType.AbsoluteStartMonthDay) &&
					(this.StartDate.MaxPossible.Ticks > SimulationTime.TicksPerMonth))
					return "Invalid month day number for start date!";
				if ((this.relativeDate == RelativeDateType.AbsoluteStartWeekDay) &&
					(this.StartDate.MaxPossible.Ticks > SimulationTime.TicksPerWeek))
					return "Invalid week day number for start date!";
				if ((this.relativeTime == RelativeTimeType.AbsoluteStopTime) && (this.Duration.MaxPossible > SimulationTime.DayTimeOffset(this.DailyHourStart, this.DailyHourEnd)))
					return "Tasks using an absolute stop time must last less than than their daily time window!";
				if (this.autoExpandToAllCrewmen && (this.numberOfCrewmenNeeded != 1))
					return "Automatic tasks cannot be assigned to multiple crew-members!";
				if (this.relativeDate == RelativeDateType.TriggeredByAnEvent)
				{
					if (this.slaveTasks.Count > 0) return "Tasks triggered by an event must not have any slaves!";
				}
				else
				{
					if (this.masterTasks.Count > 0)
						return "Only tasks triggered by an event can have masters!";
					if ((this.phaseInterruptionPolicy != PhaseInterruptionPolicies.WholePhase) &&
						(this.Duration.MaxPossible > Phase.ArbitraryMaxDuration))
						return String.Format(CultureInfo.InvariantCulture, "The duration of this task must not exceed {0} days!", Phase.ArbitraryMaxDuration.TotalDays);
					if ((!this.NoDuplicate) && (this.parallelTasks.Count > 0))
						return "Parallel tasks must disallow duplicates!";
				}
				/*foreach (var relation in this.relations)
				{
					var result = relation.ErrorMessage;
					if (!String.IsNullOrEmpty(result))
						return "Invalid relation: " + result;
				}*/
				return String.Empty;
			}
		}

		public virtual string WarningMessage
		{
			get
			{
				var result = String.Empty;
				if (((this.relativeDate == RelativeDateType.RelativeStartFromPreviousStart) || (this.relativeDate == RelativeDateType.Frequency)) &&
					(this.StartDate.Average < this.Duration.Average))
					return "Tasks using frequency, or relative to the start of the previous occurence, should have an average duration lower than the average time since the last occurence!";
				foreach (var task2 in this.parallelTasks.Values)
				{
					if ((this.onHolidays != task2.onHolidays) || (this.priority != task2.priority) ||
						(this.Duration != task2.Duration) ||
						(this.relativeDate != task2.relativeDate) || (this.relativeTime != task2.relativeTime) ||
						(this.StartDate != task2.StartDate) ||
						(this.DailyHourStart != task2.DailyHourStart) || (this.DailyHourEnd != task2.DailyHourEnd))
						result += " Inconsistent values between parallel tasks!";
					if (!task2.parallelTasks.ContainsKey(this.id))
						result += String.Format(CultureInfo.InvariantCulture, " Invalid reciprocal parallel-parallel relation with task {0}!", task2.id);
					if (task2.id == this.id)
						result += " A task should not be explicitly parallel with itself!";
				}
				if ((this.relativeDate == RelativeDateType.TriggeredByAnEvent) && (this.masterTasks.Count <= 0))
					result += " Tasks triggered by an event should have at least one master!";
				if ((this.slaveTasks.Count <= 0) && (this.taskType.IsSubCodeOf(StandardTaskType.ExternalCondition) || this.taskType.IsSubCodeOf(StandardTaskType.CriticalEvents)))
					result += " External conditions and critical events are meant to trigger at least one task (slave).";
				foreach (var task2 in this.masterTasks.Values)
					if (!task2.slaveTasks.ContainsKey(this.id))
						result += String.Format(CultureInfo.InvariantCulture, " Invalid reciprocal master-slave relation with task {0}!", task2.id);
				foreach (var task2 in this.slaveTasks.Values)
					if (!task2.masterTasks.ContainsKey(this.id))
						result += String.Format(CultureInfo.InvariantCulture, " Invalid reciprocal slave-master relation with task {0}!", task2.id);
				return result;
			}
		}

		public override string ToString()
		{
			return String.Concat(this.id, '.', this.name);
		}

		public string Details
		{
			get
			{//TODO: Use format
				var stringBuilder = new StringBuilder();
				stringBuilder.Append(this.relativeDate.ToString()).Append(": ").Append(this.StartDate.ToString()).Append("; offset: ").
					Append(this.DateOffset.Unit == TimeUnit.Undefined ? "auto" : this.DateOffset.ToString()).Append("; ").
					Append(this.relativeTime.ToString()).Append(": ").Append(this.DailyHourStart.ToStringHourUI()).Append(" ‣ ").Append(this.DailyHourEnd.ToStringHourUI()).
					Append(this.onHolidays ? " ⑦" : " ⑥").Append("; ").AppendFormat("[Interruptions: task:{0}, phase:{1}, end:{2}]; ", this.taskInterruptionPolicy, this.phaseInterruptionPolicy, this.scenarioInterruptionPolicy).
					Append(this.Duration.ToString()).Append("; Priority: ").Append(this.priority).Append("; ").
					Append(this.numberOfCrewmenNeeded).Append("CM");
				if (this.Rotation > SimulationTime.Zero)
					stringBuilder.Append(" ∞").Append(this.Rotation.TotalHours).Append('h');
				return stringBuilder.ToString();
			}
		}
	}
}
